package com.qm.console.excel;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.qm.console.component.util.BeanUtil;
import com.qm.console.component.util.EncodeUtil;
import com.qm.console.component.util.HttpInvokeUtil;
import com.qm.console.config.Config;
import com.qm.console.controller.dto.BaseRsp;
import com.qm.console.controller.dto.ResultEnum;

import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.method.HandlerMethod;

import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import lombok.extern.slf4j.Slf4j;

/**
 * //                       _oo0oo_
 * //                      o8888888o
 * //                      88" . "88
 * //                      (| -_- |)
 * //                      0\  =  /0
 * //                    ___/`---'\___
 * //                  .' \\|     |// '.
 * //                 / \\|||  :  |||// \
 * //                / _||||| -:- |||||- \
 * //               |   | \\\  -  /// |   |
 * //               | \_|  ''\---/''  |_/ |
 * //               \  .-\__  '-'  ___/-. /
 * //             ___'. .'  /--.--\  `. .'___
 * //          ."" '<  `.___\_<|>_/___.' >' "".
 * //         | | :  `- \`.;`\ _ /`;.`/ - ` : | |
 * //         \  \ `_.   \_ __\ /__ _/   .-` /  /
 * //     =====`-.____`.___ \_____/___.-`___.-'=====
 * //                       `=---='
 * //
 * //
 * //     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * //
 * //               佛祖保佑         永无BUG
 * <p>
 * com.qm.console.excel
 * Created on 2020/9/22.
 */
@Slf4j
@Service
public class ExcelService {

    private static final String EXCEL_DIR = Config.getProperty("excel.export.dir");

    private static final String EXCEL_HOST = Config.getProperty("excel.export.host");

    private static final String APP_SERVER_HOST = Config.getProperty("app.server.host");

    /**
     * 先暂时用一个map来保存正在进行的任务.
     */
    private ConcurrentHashMap<String, ExportProcess> currentTasks = new ConcurrentHashMap<>();

    /**
     * 先暂时用一个map来保存导出结果.
     */
    private ConcurrentHashMap<String, ExportResult> results = new ConcurrentHashMap<>();

    @PostConstruct
    private void onInit() {
        // init的时候清理掉所有保存的文件. todo
    }

    public boolean generateExcel(HttpServletRequest request, HttpServletResponse response, HandlerMethod h) throws IOException {
        ExportExcel exportExcel = h.getMethod().getDeclaredAnnotation(ExportExcel.class);
        if (exportExcel == null) {
            return true;
        }

//        String url = request.getRequestURL().toString();
        String url = APP_SERVER_HOST + request.getRequestURI();
        String taskKey = EncodeUtil.md5Encode(request.getRequestURL() + request.getQueryString());


        ExportExcel.OffsetLimitType offsetLimitType = exportExcel.pageType();

        Map<String, String> paramMap = new HashMap<>();
        Enumeration<String> paramKeys = request.getParameterNames();
        while (paramKeys.hasMoreElements()) {
            String key = paramKeys.nextElement();
            paramMap.put(key, request.getParameter(key));
        }
        paramMap.remove("export_excel");
        paramMap.remove(offsetLimitType.getOffset());
        paramMap.remove(offsetLimitType.getLimit());

        Enumeration<String> headers = request.getHeaderNames();
        Map<String, String> headerMap = new HashMap<>();
        while (headers.hasMoreElements()) {
            String key = headers.nextElement();
            headerMap.put(key, request.getHeader(key));
        }

        if (currentTasks.containsKey(taskKey)) {
            writeProcessingRsp(response, taskKey);
        } else if (results.containsKey(taskKey)) {
            if (paramMap.containsKey(exportExcel.endTimeKey())) {
                if (new File(EXCEL_DIR, results.get(taskKey).getResultPath()).exists()) {
                    writeFileRsp(response, results.get(taskKey));
                } else {
                    writeProcessingRsp(response, taskKey);
                    sendExportExcelEvent(taskKey,url, exportExcel, headerMap,paramMap);
                }
            } else {
                ExportResult result = results.get(taskKey);
                if (exportExcel.timeOut() + result.getFinishTime() < System.currentTimeMillis()) {
                    // 重新计算,, 写入正在计算中response.
                    writeProcessingRsp(response, taskKey);
                    sendExportExcelEvent(taskKey,url, exportExcel, headerMap,paramMap);
                } else {
                    if (new File(EXCEL_DIR, results.get(taskKey).getResultPath()).exists()) {
                        writeFileRsp(response, results.get(taskKey));
                    } else {
                        writeProcessingRsp(response, taskKey);
                        sendExportExcelEvent(taskKey,url, exportExcel, headerMap,paramMap);
                    }
                }
            }
        } else {
            writeProcessingRsp(response, taskKey);
            sendExportExcelEvent(taskKey,url, exportExcel, headerMap,paramMap);
        }
        return false;
    }

    private void sendExportExcelEvent(String key, String url, ExportExcel config, Map<String, String> headerMap, Map<String, String> paramMap) {
        BeanUtil.getApplicationContext().publishEvent(new ExportExcelEvent(key, url, config, headerMap, paramMap));
    }

    @Async
    @EventListener
    public void handleExportEvent(ExportExcelEvent event) {
        queryExcel(event.getKey(), event.getUrl(), event.getConfig(), event.getHeaderMap(), event.getParamMap());
    }

    protected void queryExcel(String taskKey, String url, ExportExcel config, Map<String, String> headerMap, Map<String, String> paramMap) {
        currentTasks.put(taskKey, new ExportProcess());
        try {
            String fileName = taskKey + ".xlsx";
            final File output = new File(EXCEL_DIR, fileName);
            if (output.exists()) {
                output.delete();
            }
            ExcelWriter writer = new ExcelWriter(output, config);
            ExportExcel.OffsetLimitType pageType = config.pageType();
            int start;
            if (pageType == ExportExcel.OffsetLimitType.DEFAULT) {
                // offset 类型是直接按cursor来的,
                start = 0;
            } else {
                // 其他page类型单次循环page + 1
                start = 1;
            }
            int limit = config.pageSize();
            new JSONArray();
            JSONArray result;
            String listKey = config.listKey();
            int totalCount = 0;
            do {
                paramMap.put(pageType.getOffset(), String.valueOf(start));
                paramMap.put(pageType.getLimit(), String.valueOf(limit));
                String body = HttpInvokeUtil.httpGet(url, paramMap, headerMap);
                JSONObject json = JSONObject.parseObject(body);
                if ((json.containsKey("code") && json.getInteger("code") == 0 )
                        || (json.containsKey("status") && json.getInteger("status") == ResultEnum.SUCCESS.getState())) {
                    if (listKey.contains(".")) {
                        // object in object.=...=.. fucker..
                        String[] listKeys = listKey.split("\\.");
                        JSONObject temp = json.getJSONObject("data");
                        for (int i = 0; i < listKeys.length - 1; i++) {
                            temp = temp.getJSONObject(listKeys[i]);
                        }
                        result = temp.getJSONArray(listKeys[listKeys.length - 1]);
                    } else {
                        result = json.getJSONObject("data").getJSONArray(listKey);
                    }

                    JSONArray flatten = new JSONArray();
                    for (int i = 0; i < result.size(); i++) {
                        flatten.add(flatten(result.getJSONObject(i)));
                    }
                    totalCount += flatten.size();
                    writer.write(flatten);
                } else {
                    result = null;
                }
                if (pageType == ExportExcel.OffsetLimitType.DEFAULT) {
                    start += limit;
                } else {
                    start++;
                }
                currentTasks.get(taskKey).setFinishedCount(totalCount);
            } while (result != null && result.size() > 0);
            writer.flush();
            results.put(taskKey, new ExportResult(fileName, System.currentTimeMillis()));
        } catch (Exception e) {
            log.error("failed to query excel", e);
        }
        currentTasks.remove(taskKey);
    }

    private JSONObject flatten(JSONObject src) {
        Set<String> keys = src.keySet();
        JSONObject result = new JSONObject();
        keys.forEach(key -> {
            Object obj = src.get(key);
            // 只支持一层的flatten.=.=.
            if (obj instanceof JSONObject) {
                Set<String> otherKeys = ((JSONObject) obj).keySet();
                otherKeys.forEach(ok -> {
                    result.put(ok, ((JSONObject) obj).get(ok));
                });
            } else {
                result.put(key, obj);
            }
        });
        return result;
    }

    private void writeFileRsp(HttpServletResponse response, ExportResult result) throws IOException {
        BaseRsp rsp = new BaseRsp(ResultEnum.EXCEL_RESULT, EXCEL_HOST + result.getResultPath());
        writeBaseRsp(response, rsp);
    }

    private void writeProcessingRsp(HttpServletResponse response, String taskKey) throws IOException {
        ExportProcess process = currentTasks.get(taskKey);
        BaseRsp rsp = new BaseRsp(ResultEnum.EXCEL_PROCESSING, process == null ? 0 : process.getFinishedCount());
        writeBaseRsp(response, rsp);
    }

    private void writeBaseRsp(HttpServletResponse response, BaseRsp rsp) throws IOException {
        response.reset();
        response.setContentType("application/json;charset=utf-8");
        response.getOutputStream().write(JSON.toJSONBytes(rsp));
        response.getOutputStream().flush();
        response.getOutputStream().close();
    }

}
